package juego;

import java.awt.Point;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import exceptions.LaserException;
import exceptions.ParedException;
import exceptions.TableroException;
import exceptions.OrigenException;

public class Tablero {
	private int filas;
	private int columnas;
	private Celda tablero[][];
	private Set<Point> posOrigen;
	private Set<Point> posDestinos;
	private String user;
	private String nivel;
	
	/**
	 * Crea un Tablero
	 * 
	 * @param filas
	 * @param columnas
	 * @param nivel
	 * 
	 * @throws TableroException lanza una excepcion del tipo TableroException
	 */
	public Tablero(int filas, int columnas, String nivel) throws TableroException{
		if( filas<5 | columnas<5 | filas>20 | columnas>20 )
			throw new TableroException();
		this.filas = filas;
		this.columnas = columnas;
		this.tablero = new Celda[filas][columnas];
		this.nivel=nivel;
		this.posOrigen = new TreeSet<Point>(new Comparator<Point>(){
			public int compare(Point p1, Point p2) {
				return 1;
			}
		});
		this.posDestinos = new TreeSet<Point>(new Comparator<Point>(){
			public int compare(Point p1, Point p2) {
				return 1;
			}
		});
	}
	
	/**
	 * Borra una celda en el tablero
	 * 
	 * @param x Posicion x en el tablero
	 * @param y Posicion y en el tablero
	 */
	public void borrarCelda(int x, int y){
		tablero[x][y]=null;
	}

	/**
	 * Agrega una celda en el tablero
	 * 
	 * @param celdaAgregada Celda que se agrega al tablero
	 * @param fila Fila en la que se agrega del tablero
	 * @param columna Columna en la que se agrega del tablero
	 */
	public boolean agregarCelda(Celda celdaAgregada, int fila, int columna){
		this.tablero[fila][columna] = celdaAgregada;
		return true;
	}
	
	/**
	 * Guarda una partida 
	 * 
	 * @param nivel Nivel en el que se encuentra la partida
	 * @param user Usuario que estaba jugando
	 * 
	 * @throws IOException
	 */
	public boolean guardarPartida(String nivel, String user ) throws IOException{
		JFileChooser fileChooser = new JFileChooser();
		int seleccion = fileChooser.showSaveDialog(fileChooser);

		if (seleccion == JFileChooser.APPROVE_OPTION)
		{
			File fichero = fileChooser.getSelectedFile();
			String pathLevel = java.lang.System.getProperty("user.dir") + "\\levels";
			if(fichero.getPath().toString().startsWith(pathLevel)){
				JOptionPane.showMessageDialog(fileChooser, 
						"No puede guardar una partida en la carpeta levels, elija otra carpeta",
						"Error carpeta levels",
						JOptionPane.ERROR_MESSAGE);
				return false;
			}else{
				if(!fichero.exists())
					fichero.createNewFile();
				BufferedWriter outputPartida = new BufferedWriter(new FileWriter(fichero));
				outputPartida.write(user+"\n");
				outputPartida.write(nivel+"\n");
				outputPartida.write(this.getFilas()+ "," + this.getColumnas()+"\n");
				for(int i=0; i<filas; i++)
					for(int j=0; j<columnas; j++)
						if(tablero[i][j]!=null)
							if(tablero[i][j].getElemento()!=null)
								outputPartida.write(tablero[i][j].toStringGuardar(i, j));
				outputPartida.close();
				return true;	
			}
		}
		return false;
	}
	
	
	/**
	 * Cuenta los puntos del juego del tablero
	 * 
	 */
	public int contarPuntos(){
		int suma=0;
		for(int i=0; i<filas; i++){
			for(int j=0; j<columnas; j++){
				if(tablero[i][j]!=null){
					if(tablero[i][j].cantLasers()>0)
						suma++;
					else if(tablero[i][j].getElemento()!=null)
						if( tablero[i][j].esOrigen())
							suma++;
				}
			}
		}
		return suma;
	}
	
	public void setUser(String user){
		this.user=user;
	}

	public String getUser(){
		return this.user;
	}
	
	public String getNivel(){
		return this.nivel;
	}
	
	/**
	 * Borra los lasers que habian sido agregados a la celda
	 * 
	 */
	public void borrarLasersDibujados(){
		for(int i=0; i<filas; i++){
			for(int j=0; j<columnas; j++){
				if (tablero[i][j]!=null){
					tablero[i][j].quitarLasers();
				}
			}
		}
	}
	
	public String toString(){
		String aux = "";
		for(Celda[] fil: this.tablero ){
			for(Celda col: fil){
				if( col != null )
					aux+=col.toString()+" ";
				else
					aux+="_ ";
			}
			aux+="\n";
		}
		return aux;
	}
	
	
	public boolean dibujar() throws LaserException{
		agregarElementos();
		try {
			boolean perdio;
			perdio = agregarLasers();
			return perdio;
		} catch (OrigenException e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Crea un Set con todas las posiciones de los origenes y otro Set con las de los destinos
	 * 
	 */
	public void agregarElementos(){
		for(int i=0; i<filas; i++ ){
			for(int j=0; j<columnas; j++){
				if( tablero[i][j] != null ){
					if( tablero[i][j].getElemento().getClass() == Origen.class){
						this.posOrigen.add(new Point(j,i));
					}else if(tablero[i][j].getElemento().getClass()==Destino.class){
						this.posDestinos.add(new Point(j,i));
					}
				}
			}
		}
	}
	
	/**
	 * Agrega todos los lasers al tablero
	 * 
	 * @throws OrigenException, LaserException
	 */
	public boolean agregarLasers() throws OrigenException, LaserException{
		boolean flag=true;
		for(Point aux: posOrigen){
			if(flag)
				flag = nuevoCamino(aux, (Origen)tablero[aux.y][aux.x].getElemento());
		}
		return flag;
	}
	
	/**
	 * Crea un nuevo camino de lasers
	 * 
	 * @throws OrigenException, LaserException
	 */
	private boolean nuevoCamino( Point p, Origen orig ) throws OrigenException, LaserException{
		int prox_x=p.x;
		int prox_y=p.y;
		int rot[]= new int[4];
		int am=0;
		boolean flag = true;
		rot[1]=orig.getRotacion();
		int rotA=rot[1]; //rotacion con la que venia el laser

		switch(rotA){
		case 0:
			prox_x++;
			break;
		case 1: 
			prox_y++;
			break;
		case 2: 
			prox_x--;
			break;
		case 3: 
			prox_y--;
			break;
		default:
			prox_x=this.columnas;
			break;
		}	
		while(prox_x<this.columnas & prox_x>=0 & prox_y<this.filas & prox_y>=0 ){
			if( tablero[prox_y][prox_x] == null )
				tablero[prox_y][prox_x] = new Celda();

			if(tablero[prox_y][prox_x].getElemento()!=null){ //si hay un elemento
				rot=tablero[prox_y][prox_x].getElemento().getReturn(rotA);
			}
			else{
				rot = new int[2];
				rot[0]=1;
				rot[1]=rotA;
			}
			
			am=rot[0];
			if(am==-1){ //termina el juego
				prox_x=this.columnas+1; //condicion para el corte
				return false;
			}
			else if(am==0){ //no va el laser
				prox_x=this.columnas+1; //condicion para el corte
			}
			else if(am==1){ //laser
				Laser laser = new Laser(rot[1], orig.getColor().getBlue(), orig.getColor().getGreen(),orig.getColor().getRed());
				tablero[prox_y][prox_x].agregarLaser(laser); //agrego el laser
			}
			else if(am>=2){ //si era espejo
				for(int i=1; i<=am; i++){
					if(rot[i]!=-1){
						HalfLaser hLaser = new HalfLaser((rot[i]<2?rot[i]+2:rot[i]-2), orig.getColor().getBlue(), orig.getColor().getGreen(),orig.getColor().getRed());
						tablero[prox_y][prox_x].agregarLaser(hLaser); //agrego el laser
						//halfLaser
					}
				}
				if(am==3){ //si era semi-espejo
					flag = nuevoCamino(new Point(prox_x,prox_y), new Origen((rot[3]<2?rot[3]+2:rot[3]-2),orig.getColor().getRed(), orig.getColor().getGreen(), orig.getColor().getBlue()));
				}
				rotA=rot[2]; //rotacion con la que sigue el laser 
			}

			switch(rotA){
			case 0:
				prox_x++;
				break;
			case 1: 
				prox_y++;
				break;
			case 2: 
				prox_x--;
				break;
			case 3: 
				prox_y--;
				break;
			default:
				prox_x=this.columnas;
				break;
			}	
		}
		return flag;
	}
	
	public int getFilas(){
		return filas;
	}
	
	public int getColumnas(){
		return columnas;
	}
	
	public Celda[][] getTablero(){
		return tablero;
	}

	/**
	 * Valida la victoria y si gano devuelve el puntaje
	 * 
	 */
	public int validarVictoria() {
		Destino unDestino = null;
		boolean gano=true;
		boolean flag=false;
		for(Point aux: posDestinos){
			flag=true;
			unDestino = (Destino) tablero[aux.y][aux.x].getElemento();
			gano = gano & tablero[aux.y][aux.x].hayLaserColor(unDestino.getColor());
		}
		if(gano&flag)
			return contarPuntos(); //devuelve el puntaje
		return -1; // no gano
	}	
}